/**
 * Copyright 2022 Beijing Douyin Information Service Co., Ltd. SPDX-License-Identifier: BSD-3-Clause
 *
 * <p>Package com.bytedance.microapp Sign 签名算法
 *
 * <p>Package com.bytedance.microapp Sign implement the signature algorithm
 */
package com.sdweizan.sdk.bytedance.util;

import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.time.Instant;
import java.util.*;
import java.util.stream.Collectors;

public class SignUtils {

  /**
   * 担保支付请求不参与签名参数 app_id 小程序appID thirdparty_id 代小程序进行该笔交易调用的第三方平台服务商id sign 签名 other_settle_params
   * 其他分账方参数
   *
   * <p>Guaranteed payment requests do not participate in signature parameters app_id Applets appID
   * thirdparty_id The id of the third-party platform service provider that calls the transaction on
   * behalf of the Applets sign sign other_settle_params Other settle params
   */
  public static final List<String> REQUEST_NOT_NEED_SIGN_PARAMS =
      Arrays.asList("app_id", "thirdparty_id", "sign", "other_settle_params");

  /**
   * RequestSign 担保支付请求签名算法
   *
   * @param requestBody {@code Map<String, Object>} 所有的请求参数
   * @return：签名字符串
   *     <p>RequestSign Guaranteed payment request signature algorithm
   * @param requestBody {@code Map<String, Object>} all request parameters
   * @return: Signature string
   */
  public static String requestSign(Map<String, Object> requestBody, String salt) {
    List<String> paramsArray =
        requestBody.entrySet().stream()
            .filter(entry -> !REQUEST_NOT_NEED_SIGN_PARAMS.contains(entry.getKey()))
            .filter(entry -> entry.getValue() != null)
            .map(entry -> String.valueOf(entry.getValue()))
            .filter(value -> !"".equals(value.trim()))
            .collect(Collectors.toList());
    //    for (Map.Entry<?, ?> entry : requestBody.entrySet()) {
    //      String value = entry.getValue().toString().trim();
    //      if (value.startsWith("\"") && value.endsWith("\"") && value.length() > 1) {
    //        value = value.substring(1, value.length() - 1);
    //      }
    //      paramsArray.add(value.trim());
    //    }
    paramsArray.add(salt);
    Collections.sort(paramsArray);
    return md5FromStr(String.join("&", paramsArray));
  }

  /**
   * CallbackSign 担保支付回调签名算法
   *
   * @param params {@code List<String>} 所有字段（验证时注意不包含 sign 签名本身，不包含空字段与 type 常量字段）内容与平台上配置的 token
   * @return：签名字符串
   *     <p>CallbackSign Guaranteed payment callback signature algorithm
   * @param params {@code List<String>} The content of all fields (note that the sign signature
   *     itself is not included during verification, and does not include empty fields and type
   *     constant fields) content and the token configured on the platform
   * @return: signature string
   */
  public static String callbackSign(List<String> params) {
    try {
      String concat = params.stream().sorted().collect(Collectors.joining(""));
      byte[] arrayByte = concat.getBytes(StandardCharsets.UTF_8);
      MessageDigest mDigest = MessageDigest.getInstance("SHA1");
      byte[] digestByte = mDigest.digest(arrayByte);

      StringBuffer signBuilder = new StringBuffer();
      for (byte b : digestByte) {
        signBuilder.append(Integer.toString((b & 0xff) + 0x100, 16).substring(1));
      }
      return signBuilder.toString();
    } catch (Exception exp) {
      return "";
    }
  }

  /**
   * md5FromStr md5算法对该字符串计算摘要
   *
   * @param inStr {@code String} 需要签名的字符串
   * @return：签名字符串
   *     <p>md5FromStr The md5 algorithm computes a digest of the string
   * @param inStr {@code String} String to be signed
   * @return: signature string
   */
  private static String md5FromStr(String inStr) {
    MessageDigest md5;
    try {
      md5 = MessageDigest.getInstance("MD5");
    } catch (NoSuchAlgorithmException e) {
      e.printStackTrace();
      return "";
    }

    byte[] byteArray = inStr.getBytes(StandardCharsets.UTF_8);
    byte[] md5Bytes = md5.digest(byteArray);
    StringBuilder hexValue = new StringBuilder();
    for (byte md5Byte : md5Bytes) {
      int val = ((int) md5Byte) & 0xff;
      if (val < 16) {
        hexValue.append("0");
      }
      hexValue.append(Integer.toHexString(val));
    }
    return hexValue.toString();
  }

  /** RequestSign 通用交易请求-下单签名算法 */
  public static String getByteAuthorization(
      String requestWay, String uri, String data, String appId, String privateKeyStr) {
    long timestamp = Instant.now().getEpochSecond();
    String nonceStr = UUID.randomUUID().toString();
    String keyVersion = "1";
    String byteAuthorization;
    try {
      // 生成签名
      String signature = getSignature(privateKeyStr, requestWay, uri, timestamp, nonceStr, data);
      // 构造byteAuthorization
      StringBuilder sb = new StringBuilder();
      sb.append("SHA256-RSA2048 ")
          .append("appid=")
          .append(appId)
          .append(",")
          .append("nonce_str=")
          .append(nonceStr)
          .append(",")
          .append("timestamp=")
          .append(timestamp)
          .append(",")
          .append("key_version=")
          .append(keyVersion)
          .append(",")
          .append("signature=")
          .append(signature);
      byteAuthorization = sb.toString();
    } catch (Exception ex) {
      ex.printStackTrace();
      return "";
    }
    return byteAuthorization;
  }

  public static String getSignature(
      String privateKeyStr, String method, String uri, long timestamp, String nonce, String data)
      throws Exception {
    String rawStr = method + "\n" + uri + "\n" + timestamp + "\n" + nonce + "\n" + data + "\n";
    System.out.println(rawStr);
    Signature sign = Signature.getInstance("SHA256withRSA");
    sign.initSign(string2PrivateKey(privateKeyStr));
    sign.update(rawStr.getBytes(StandardCharsets.UTF_8));
    return Base64.getEncoder().encodeToString(sign.sign());
  }

  public static PrivateKey string2PrivateKey(String privateKeyStr) {
    PrivateKey prvKey = null;
    try {
      byte[] privateBytes = Base64.getDecoder().decode(privateKeyStr);
      PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateBytes);
      KeyFactory keyFactory = KeyFactory.getInstance("RSA");
      prvKey = keyFactory.generatePrivate(keySpec);
    } catch (Exception ex) {
      ex.printStackTrace();
    }
    return prvKey;
  }
}
